package TemperaturePackage;

import be.kuleuven.cs.som.annotate.*;

/**
 * A class of temperatures involving a numeral and a unit.
 * 
 * @invar The numeral of this temperature must be valid. |isValidNumeral()
 * @version 1.0
 * @author Jessa Bekker en Esther Alberts
 */

@Value
public class Temperature {

	/**
	 * Initialize this new Temperature with given numeral and given unit.
	 * 
	 * @param numeral
	 *            The value of the temperature
	 * @param temperaturUnit
	 *            The unit in which the value is given
	 * @post The new value of this temperature is equal to the given value
	 *       |new.getNumeral() == numeral
	 * @post The new unit of this temperature is equal to the given unit
	 *       |new.getTemperatureUnit() == temperatureUnit
	 * @throws IllegalArgumentException
	 *             The given unit is not effective. |(unit != null)
	 *             IllegalArgumentException The given numeral must be valid.
	 *             |!isValidNumeral()
	 */
	public Temperature(double numeral, TemperatureUnit unit)
			throws IllegalArgumentException {
		this.numeral = numeral;
		this.unit = unit;
		if (unit == null)
			throw new IllegalArgumentException("Not effective unit!");
		if (!isValidNumeral(numeral))
			throw new IllegalArgumentException(
					"The numeral must be smaller than 1000000000!");
	}

	/**
	 * Return the numeral of this temperature.
	 */
	@Basic
	@Immutable
	public double getNumeral() {
		return this.numeral;
	}

	/**
	 * Check whether the numeral is a valid numeral for this temperature
	 * (slightly dependant on the unit)
	 * 
	 * @param numeral
	 *            the numeral to check.
	 * @return True if and only if the numeral is smaller than one billion and
	 *         larger than minus one billion if the unit would be converted to
	 *         celsius. (which assures us to have no under- or overflow) |result
	 *         == (unit == TemperatureUnit.CELSIUS && numeral < 1000000000 &&
	 *         numeral > -1000000000)|| |(unit == TemperatureUnit.FAHRENHEIT &&
	 *         numeral < (1000000000 * 1.8)-32 && numeral > -1800000032)||
	 *         |(unit == TemperatureUnit.KELVIN && numeral < 1000000273.15 &&
	 *         numeral > -999999726.85)
	 */
	public boolean isValidNumeral(double numeral) {
		return (unit == TemperatureUnit.CELSIUS && numeral < 1000000000 && numeral > -1000000000)
				|| (unit == TemperatureUnit.FAHRENHEIT
						&& numeral < (1000000000 * 1.8) - 32 && numeral > -1800000032)
				|| (unit == TemperatureUnit.KELVIN && numeral < 1000000273.15 && numeral > -999999726.85);
	}

	/**
	 * Variable registering the value of this temperature.
	 */
	private final double numeral;

	/**
	 * Return the unit of this temperature.
	 */
	@Basic
	@Immutable
	public TemperatureUnit getUnit() {
		return this.unit;
	}

	/**
	 * Return a temperature that has the same value as this temperature
	 * expressed in the given unit.
	 * 
	 * @param unit
	 *            The unit to convert to
	 * @return The resulting temperature is effective |result != null
	 * @return The resulting temperature has the given unit as its unit.
	 *         |result.getUnit() == unit
	 * @return The numeral of the resulting temperature is equal to the numeral
	 *         of this temperature multiplied with the multiplier from the unit
	 *         of this temperature to the given unit plus a constant from the
	 *         unit of this temperature. |let | multiplier =
	 *         this.getUnit().getExchangeConstants(unit)[0] | constant =
	 *         this.getUnit().getExchangeConstants(unit)[1] | numeralInUnit =
	 *         this.getNumeral()*multiplier + constant |in |
	 *         result.getNumeral.equals(numeralInUnit)
	 * @throws IllegalArgumentException
	 *             the given unit is not effective |unit == null
	 */
	public Temperature toUnit(TemperatureUnit unit)
			throws IllegalArgumentException {
		if (unit == null)
			throw new IllegalArgumentException("Non-effective unit!");
		if (this.getUnit() == unit)
			return this;
		double multiplier = this.getUnit().getExchangeConstants(unit)[0];
		double constant = this.getUnit().getExchangeConstants(unit)[1];
		double numeralInUnit = this.getNumeral() * multiplier + constant;
		return new Temperature(numeralInUnit, unit);
	}

	/**
	 * Variable registering the unit of this temperature.
	 */
	private final TemperatureUnit unit;

	/**
	 * Check whether this Temperature is equal to the given object.
	 * 
	 * @return True if and only if the given object is effective, and if this
	 *         temperature and the given object belong to the same class, and if
	 *         this temperature and the other object interpreted as a
	 *         temperature have an equal numeral and an equal temperature unit.
	 *         | result == | ( (other != null) | && (this.getClass() ==
	 *         other.getClass()) | && (this.getNumeral().equals( | ((Temperature
	 *         other).getNumeral())) | && (this.getUnit().equals( |
	 *         ((Temperature other).getUnit()))
	 */
	@Override
	public boolean equals(Object other) {
		if (other == null)
			return false;
		if (this.getClass() != other.getClass())
			return false;
		Temperature otherTemp = (Temperature) other;
		return ((this.getNumeral() == otherTemp.getNumeral()) && (this
				.getUnit() == otherTemp.getUnit()));
	}

	/**
	 * Return the hash code for this temperature.
	 */
	@Override
	public int hashCode() {
		return (int) getNumeral() + getUnit().hashCode();
	}

	/**
	 * Return a textual representation of this temperature.
	 * 
	 * @return A string consisting of the textual representation of the numeral
	 *         of this temperature, followed by the textual representation of
	 *         its unit, separated by a space and enclosed in square brackets. |
	 *         result.equals( | "["+ getNumeral() +" " + getUnit() + "]"
	 */
	@Override
	public String toString() {
		return "[" + getNumeral() + " " + getUnit() + "]";
	}

}